// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/formats/webm/webm_content_encodings_client.h"

#include <stdint.h>

#include <string>

#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/mock_media_log.h"
#include "media/formats/webm/webm_constants.h"
#include "media/formats/webm/webm_parser.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::StrictMock;

namespace media {

// Matchers for verifying common media log entry strings.
MATCHER(MissingContentEncoding, "")
{
    return CONTAINS_STRING(arg, "Missing ContentEncoding.");
}

MATCHER(UnexpectedContentEncodingOrder, "")
{
    return CONTAINS_STRING(arg, "Unexpected ContentEncodingOrder.");
}

MATCHER(UnexpectedContentEncodingScope, "")
{
    return CONTAINS_STRING(arg, "Unexpected ContentEncodingScope.");
}

MATCHER(ContentCompressionNotSupported, "")
{
    return CONTAINS_STRING(arg, "ContentCompression not supported.");
}

MATCHER(MissingContentEncryption, "")
{
    return CONTAINS_STRING(
        arg,
        "ContentEncodingType is encryption but ContentEncryption is missing.");
}

MATCHER_P(UnexpectedContentEncAlgo, algo, "")
{
    return CONTAINS_STRING(
        arg, "Unexpected ContentEncAlgo " + base::IntToString(algo) + ".");
}

class WebMContentEncodingsClientTest : public testing::Test {
public:
    WebMContentEncodingsClientTest()
        : media_log_(new StrictMock<MockMediaLog>())
        , client_(media_log_)
        , parser_(kWebMIdContentEncodings, &client_)
    {
    }

    void ParseAndExpectToFail(const uint8_t* buf, int size)
    {
        int result = parser_.Parse(buf, size);
        EXPECT_EQ(-1, result);
    }

protected:
    scoped_refptr<StrictMock<MockMediaLog>> media_log_;
    WebMContentEncodingsClient client_;
    WebMListParser parser_;
};

TEST_F(WebMContentEncodingsClientTest, EmptyContentEncodings)
{
    const uint8_t kContentEncodings[] = {
        0x6D, 0x80, 0x80, // ContentEncodings (size = 0)
    };
    int size = sizeof(kContentEncodings);
    EXPECT_MEDIA_LOG(MissingContentEncoding());
    ParseAndExpectToFail(kContentEncodings, size);
}

TEST_F(WebMContentEncodingsClientTest, EmptyContentEncoding)
{
    const uint8_t kContentEncodings[] = {
        0x6D, 0x80, 0x83, // ContentEncodings (size = 3)
        0x63, 0x40, 0x80, //   ContentEncoding (size = 0)
    };
    int size = sizeof(kContentEncodings);
    ParseAndExpectToFail(kContentEncodings, size);
}

TEST_F(WebMContentEncodingsClientTest, SingleContentEncoding)
{
    const uint8_t kContentEncodings[] = {
        0x6D,
        0x80,
        0xA1, // ContentEncodings (size = 33)
        0x62,
        0x40,
        0x9e, //   ContentEncoding (size = 30)
        0x50,
        0x31,
        0x81,
        0x00, //     ContentEncodingOrder (size = 1)
        0x50,
        0x32,
        0x81,
        0x01, //     ContentEncodingScope (size = 1)
        0x50,
        0x33,
        0x81,
        0x01, //     ContentEncodingType (size = 1)
        0x50,
        0x35,
        0x8F, //     ContentEncryption (size = 15)
        0x47,
        0xE1,
        0x81,
        0x05, //       ContentEncAlgo (size = 1)
        0x47,
        0xE2,
        0x88, //       ContentEncKeyID (size = 8)
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
    };
    int size = sizeof(kContentEncodings);

    int result = parser_.Parse(kContentEncodings, size);
    ASSERT_EQ(size, result);
    const ContentEncodings& content_encodings = client_.content_encodings();

    ASSERT_EQ(1u, content_encodings.size());
    ASSERT_TRUE(content_encodings[0]);
    EXPECT_EQ(0, content_encodings[0]->order());
    EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
        content_encodings[0]->scope());
    EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
    EXPECT_EQ(ContentEncoding::kEncAlgoAes,
        content_encodings[0]->encryption_algo());
    EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size());
}

TEST_F(WebMContentEncodingsClientTest, MultipleContentEncoding)
{
    const uint8_t kContentEncodings[] = {
        0x6D,
        0x80,
        0xC2, // ContentEncodings (size = 66)
        0x62,
        0x40,
        0x9e, //   ContentEncoding (size = 30)
        0x50,
        0x31,
        0x81,
        0x00, //     ContentEncodingOrder (size = 1)
        0x50,
        0x32,
        0x81,
        0x03, //     ContentEncodingScope (size = 1)
        0x50,
        0x33,
        0x81,
        0x01, //     ContentEncodingType (size = 1)
        0x50,
        0x35,
        0x8F, //     ContentEncryption (size = 15)
        0x47,
        0xE1,
        0x81,
        0x05, //       ContentEncAlgo (size = 1)
        0x47,
        0xE2,
        0x88, //       ContentEncKeyID (size = 8)
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0x62,
        0x40,
        0x9e, //   ContentEncoding (size = 30)
        0x50,
        0x31,
        0x81,
        0x01, //     ContentEncodingOrder (size = 1)
        0x50,
        0x32,
        0x81,
        0x03, //     ContentEncodingScope (size = 1)
        0x50,
        0x33,
        0x81,
        0x01, //     ContentEncodingType (size = 1)
        0x50,
        0x35,
        0x8F, //     ContentEncryption (size = 15)
        0x47,
        0xE1,
        0x81,
        0x01, //       ContentEncAlgo (size = 1)
        0x47,
        0xE2,
        0x88, //       ContentEncKeyID (size = 8)
        0xBB,
        0xBB,
        0xBB,
        0xBB,
        0xBB,
        0xBB,
        0xBB,
        0xBB,
    };
    int size = sizeof(kContentEncodings);

    int result = parser_.Parse(kContentEncodings, size);
    ASSERT_EQ(size, result);
    const ContentEncodings& content_encodings = client_.content_encodings();
    ASSERT_EQ(2u, content_encodings.size());

    for (int i = 0; i < 2; ++i) {
        ASSERT_TRUE(content_encodings[i]);
        EXPECT_EQ(i, content_encodings[i]->order());
        EXPECT_EQ(ContentEncoding::kScopeAllFrameContents | ContentEncoding::kScopeTrackPrivateData,
            content_encodings[i]->scope());
        EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[i]->type());
        EXPECT_EQ(!i ? ContentEncoding::kEncAlgoAes : ContentEncoding::kEncAlgoDes,
            content_encodings[i]->encryption_algo());
        EXPECT_EQ(8u, content_encodings[i]->encryption_key_id().size());
    }
}

TEST_F(WebMContentEncodingsClientTest, DefaultValues)
{
    const uint8_t kContentEncodings[] = {
        0x6D, 0x80, 0x8A, // ContentEncodings (size = 10)
        0x62, 0x40, 0x87, //   ContentEncoding (size = 7)
        //     ContentEncodingOrder missing
        //     ContentEncodingScope missing
        0x50, 0x33, 0x81, 0x01, //     ContentEncodingType (size = 1)
        0x50, 0x35, 0x80, //     ContentEncryption (size = 0)
        //     ContentEncAlgo missing
    };
    int size = sizeof(kContentEncodings);

    int result = parser_.Parse(kContentEncodings, size);
    ASSERT_EQ(size, result);
    const ContentEncodings& content_encodings = client_.content_encodings();

    ASSERT_EQ(1u, content_encodings.size());
    ASSERT_TRUE(content_encodings[0]);
    EXPECT_EQ(0, content_encodings[0]->order());
    EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
        content_encodings[0]->scope());
    EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
    EXPECT_EQ(ContentEncoding::kEncAlgoNotEncrypted,
        content_encodings[0]->encryption_algo());
    EXPECT_TRUE(content_encodings[0]->encryption_key_id().empty());
}

TEST_F(WebMContentEncodingsClientTest, ContentEncodingsClientReuse)
{
    const uint8_t kContentEncodings[] = {
        0x6D,
        0x80,
        0xA1, // ContentEncodings (size = 33)
        0x62,
        0x40,
        0x9e, //   ContentEncoding (size = 30)
        0x50,
        0x31,
        0x81,
        0x00, //     ContentEncodingOrder (size = 1)
        0x50,
        0x32,
        0x81,
        0x01, //     ContentEncodingScope (size = 1)
        0x50,
        0x33,
        0x81,
        0x01, //     ContentEncodingType (size = 1)
        0x50,
        0x35,
        0x8F, //     ContentEncryption (size = 15)
        0x47,
        0xE1,
        0x81,
        0x05, //       ContentEncAlgo (size = 1)
        0x47,
        0xE2,
        0x88, //       ContentEncKeyID (size = 8)
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
    };
    int size = sizeof(kContentEncodings);

    // Parse for the first time.
    int result = parser_.Parse(kContentEncodings, size);
    ASSERT_EQ(size, result);

    // Parse again.
    parser_.Reset();
    result = parser_.Parse(kContentEncodings, size);
    ASSERT_EQ(size, result);
    const ContentEncodings& content_encodings = client_.content_encodings();

    ASSERT_EQ(1u, content_encodings.size());
    ASSERT_TRUE(content_encodings[0]);
    EXPECT_EQ(0, content_encodings[0]->order());
    EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
        content_encodings[0]->scope());
    EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
    EXPECT_EQ(ContentEncoding::kEncAlgoAes,
        content_encodings[0]->encryption_algo());
    EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size());
}

TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingOrder)
{
    const uint8_t kContentEncodings[] = {
        0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
        0x62, 0x40, 0x8B, //   ContentEncoding (size = 11)
        0x50, 0x31, 0x81, 0xEE, //     ContentEncodingOrder (size = 1), invalid
        0x50, 0x33, 0x81, 0x01, //     ContentEncodingType (size = 1)
        0x50, 0x35, 0x80, //     ContentEncryption (size = 0)
    };
    int size = sizeof(kContentEncodings);
    EXPECT_MEDIA_LOG(UnexpectedContentEncodingOrder());
    ParseAndExpectToFail(kContentEncodings, size);
}

TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingScope)
{
    const uint8_t kContentEncodings[] = {
        0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
        0x62, 0x40, 0x8B, //   ContentEncoding (size = 11)
        0x50, 0x32, 0x81, 0xEE, //     ContentEncodingScope (size = 1), invalid
        0x50, 0x33, 0x81, 0x01, //     ContentEncodingType (size = 1)
        0x50, 0x35, 0x80, //     ContentEncryption (size = 0)
    };
    int size = sizeof(kContentEncodings);
    EXPECT_MEDIA_LOG(UnexpectedContentEncodingScope());
    ParseAndExpectToFail(kContentEncodings, size);
}

TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingType)
{
    const uint8_t kContentEncodings[] = {
        0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
        0x62, 0x40, 0x8B, //   ContentEncoding (size = 11)
        0x50, 0x33, 0x81, 0x00, //     ContentEncodingType (size = 1), invalid
        0x50, 0x35, 0x80, //     ContentEncryption (size = 0)
    };
    int size = sizeof(kContentEncodings);
    EXPECT_MEDIA_LOG(ContentCompressionNotSupported());
    ParseAndExpectToFail(kContentEncodings, size);
}

// ContentEncodingType is encryption but no ContentEncryption present.
TEST_F(WebMContentEncodingsClientTest, MissingContentEncryption)
{
    const uint8_t kContentEncodings[] = {
        0x6D, 0x80, 0x87, // ContentEncodings (size = 7)
        0x62, 0x40, 0x84, //   ContentEncoding (size = 4)
        0x50, 0x33, 0x81, 0x01, //     ContentEncodingType (size = 1)
        //     ContentEncryption missing
    };
    int size = sizeof(kContentEncodings);
    EXPECT_MEDIA_LOG(MissingContentEncryption());
    ParseAndExpectToFail(kContentEncodings, size);
}

TEST_F(WebMContentEncodingsClientTest, InvalidContentEncAlgo)
{
    const uint8_t kContentEncodings[] = {
        0x6D,
        0x80,
        0x99, // ContentEncodings (size = 25)
        0x62,
        0x40,
        0x96, //   ContentEncoding (size = 22)
        0x50,
        0x33,
        0x81,
        0x01, //     ContentEncodingType (size = 1)
        0x50,
        0x35,
        0x8F, //     ContentEncryption (size = 15)
        0x47,
        0xE1,
        0x81,
        0xEE, //       ContentEncAlgo (size = 1), invalid
        0x47,
        0xE2,
        0x88, //       ContentEncKeyID (size = 8)
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
        0xAA,
    };
    int size = sizeof(kContentEncodings);
    EXPECT_MEDIA_LOG(UnexpectedContentEncAlgo(0xEE));
    ParseAndExpectToFail(kContentEncodings, size);
}

} // namespace media
